﻿using System;
using Aeon.Emulator.Decoding;

namespace Aeon.Emulator
{
    /// <summary>
    /// Contains information about a decoded instruction.
    /// </summary>
    [CLSCompliant(false)]
    public sealed class OpcodeInfo : IEquatable<OpcodeInfo>
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the OpcodeInfo class.
        /// </summary>
        /// <param name="instInfo">InstructionInfo struct from which the OpcodeInfo class is initialized.</param>
        internal OpcodeInfo(InstructionInfo instInfo)
        {
            this.Opcode = instInfo.Opcode;
            this.ModRmInfo = instInfo.ModRmByte;
            this.extendedOpcode = instInfo.ExtendedRmOpcode;
            this.operands = instInfo.Operands;
            this.Emulators = instInfo.NewEmulators;
            this.name = instInfo.Name;
            this.isPrefix = instInfo.IsPrefix;
            this.length = instInfo.IsMultiByte ? 2 : 1;
            this.emulateMethods = instInfo.EmulateMethods;
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets the decoded instruction's operands.
        /// </summary>
        public OperandFormat Operands
        {
            get { return operands; }
        }
        /// <summary>
        /// Gets the name of the instruction.
        /// </summary>
        public string Name
        {
            get { return name; }
        }
        /// <summary>
        /// Gets a value indicating whether the instruction is a prefix.
        /// </summary>
        public bool IsPrefix
        {
            get { return isPrefix; }
        }
        /// <summary>
        /// Gets the length of the opcode in bytes.
        /// </summary>
        public int Length
        {
            get { return length; }
        }
        #endregion

        #region Internal Properties
        /// <summary>
        /// Gets the descriptors for the method which perform the emulation of this opcode.
        /// </summary>
        public System.Reflection.MethodInfo[] EmulateMethods
        {
            get { return this.emulateMethods; }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Tests for equality with another OpcodeInfo instance.
        /// </summary>
        /// <param name="other">Other OpcodeInfo instance to test.</param>
        /// <returns>True if objects are equal; otherwise false.</returns>
        public bool Equals(OpcodeInfo other)
        {
            return this.Opcode == other.Opcode && this.ModRmInfo == other.ModRmInfo;
        }
        /// <summary>
        /// Tests for equality with another object.
        /// </summary>
        /// <param name="obj">Other object to test.</param>
        /// <returns>True if objects are equal; otherwise false.</returns>
        public override bool Equals(object obj)
        {
            OpcodeInfo other = obj as OpcodeInfo;
            if(other != null)
                return Equals(other);
            else
                return false;
        }
        /// <summary>
        /// Gets a hash code for the OpcodeInfo instance.
        /// </summary>
        /// <returns>Hash code for the OpcodeInfo instance.</returns>
        public override int GetHashCode()
        {
            return this.Opcode.GetHashCode();
        }
        /// <summary>
        /// Gets a formatted string representation of the OpcodeInfo instance.
        /// </summary>
        /// <returns>Formatted string representation of the OpcodeInfo instance.</returns>
        public override string ToString()
        {
            if(ModRmInfo == ModRmInfo.OnlyRm)
            {
                if(this.Opcode <= 0xFF)
                    return string.Format("{0} ({1:X2}/{2})", name, Opcode, extendedOpcode);
                else
                    return string.Format("{0} ({1:X4}/{2})", name, Opcode, extendedOpcode);
            }
            else if(this.Opcode <= 0xFF)
                return string.Format("{0} ({1:X2})", name, Opcode);
            else
                return string.Format("{0} ({1:X4})", name, this.Opcode);
        }
        /// <summary>
        /// Gets the flow direction of an operand.
        /// </summary>
        /// <param name="operandIndex">Index of the operand.</param>
        /// <returns>Flow direction of the operand.</returns>
        public CodeOperandFlow GetOperandFlowDirection(int operandIndex)
        {
            var info = this.emulateMethods[0] ?? this.emulateMethods[1] ?? this.emulateMethods[2] ?? this.emulateMethods[3];
            var args = info.GetParameters();
            var i = operandIndex + 1;
            if(i < args.Length)
            {
                if(args[i].IsOut)
                    return CodeOperandFlow.Out;
                else if(args[i].ParameterType.IsByRef)
                    return CodeOperandFlow.InOut;
                else
                    return CodeOperandFlow.In;
            }

            throw new ArgumentException("Invalid operand index.");
        }
        #endregion

        #region Public Fields
        /// <summary>
        /// The instruction's opcode.
        /// </summary>
        public readonly ushort Opcode;
        /// <summary>
        /// The instruction's ModR/M value information.
        /// </summary>
        public readonly ModRmInfo ModRmInfo;
        /// <summary>
        /// Gets performance statistics for the opcode.
        /// </summary>
        public readonly OpcodeInstrumentation Statistics = new OpcodeInstrumentation();
        #endregion

        #region Internal Fields
        /// <summary>
        /// Delegates to emulate methods.
        /// </summary>
        internal readonly Decoding.DecodeAndEmulate[] Emulators;
        #endregion

        #region Private Fields
        private readonly byte extendedOpcode;
        private readonly bool isPrefix;
        private readonly OperandFormat operands;
        private readonly string name;
        private readonly int length;
        private readonly System.Reflection.MethodInfo[] emulateMethods;
        #endregion
    }
}
